package com.kamistoat.meimeimall.meimeimallproduct.web;

import com.kamistoat.meimeimall.common.annotation.DubboAndHTTPExceptionCatch;
import com.kamistoat.meimeimall.common.dubboInterface.SecKillDubboInterface;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import com.kamistoat.meimeimall.common.utils.R;
import com.kamistoat.meimeimall.meimeimallproduct.entity.CategoryEntity;
import com.kamistoat.meimeimall.meimeimallproduct.service.CategoryService;
import com.kamistoat.meimeimall.meimeimallproduct.vo.thymeleaf.Catalog2Vo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.dubbo.config.annotation.Reference;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Api(tags = "首页操作")
@RestController
@RequestMapping("web")
public class HomePageController {
    @Autowired
    CategoryService categoryService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;

    @Reference
    SecKillDubboInterface secKillDubboInterface;

    /**
     * 带缓查询所有一级分类
     */
    @ApiOperation("带缓查询所有一级菜单")
    @GetMapping("/home_page_top_category")
    @DubboAndHTTPExceptionCatch
    public R getTopCategory() {
        // 只查询一级分类，在Category服务层创建这个方法
        List<CategoryEntity> topCategoryList = categoryService.getTopCategorys();
        return R.ok().put("topCategoryList", topCategoryList);
    }

    /**
     * 带缓查询所有二级和三级分类
     */
    @ApiOperation("带缓查询所有二级和三级分类")
    @GetMapping("/home_page_second_category")
    public R getSecondCategory() {
        Map<String, List<Catalog2Vo>> secondCategoryList = categoryService.getSecondCategorys();
        return R.ok().put("secondCategoryList", secondCategoryList);
    }

    /**
     * 带缓查询所有分类，以树形结构返回
     */
    @ApiOperation("带缓查询所有分类。并以树形结构进行返回")
    @GetMapping("/home_page_tree_category")
    @DubboAndHTTPExceptionCatch
    public R getTreeCategory() {
        List<CategoryEntity> treeCategoryList = categoryService.listWithTree();
        return R.ok().put("treeCategoryList", treeCategoryList);
    }

    /**
     * 点击一级或二级商品分类时，应进入对应的商品分类欢迎页
     * @param categoryId 商品分类Id
     * @return
     */
    @ApiOperation("点击一级或二级商品分类时，应进入对应的商品分类欢迎页")
    @GetMapping("/home_page_top_category/{categoryId}")
    @DubboAndHTTPExceptionCatch
    public R entryTopCategory(@PathVariable("categoryId") Long categoryId) {
        int level = categoryService.getCategoryLevel(categoryId);
        if(level == 1 || level == 2) {
            return R.ok().put("跳转到分类" + categoryId + "的欢迎页", "...");
        }else {
            throw new RRException(BizCodeEnum.CATEGORY_ID_BANNED.getMessage(), BizCodeEnum.CATEGORY_ID_BANNED.getCode());
        }
    }


    /**
     * 配合Sentinel流控。当流量过大时，进入等待页面
     * @return
     */
    @GetMapping("/blockWait.html")
    public String blockWait(){
        return "blockWait";
    }


    /**
     * 一个简单服务，用于测试nginx和gateway中间件的性能
     * 用@ResponseBody规定返回值就是hello
     * 如果去掉@ResponseBody，则返回页面hello.html
     *
     * @return 返回String
     */
    public String hello() {
        // 直接调用RedissonClient.getLock("任意指定的锁定名称")
        RLock mylock = redissonClient.getLock("my-lock");
        // 执行加锁
        mylock.lock();
        System.out.println("加锁成功" + Thread.currentThread().getId());
        try {
            System.out.println("用try包含业务逻辑，无论是否成功，都要执行解锁" +
                    Thread.currentThread().getId());
            Thread.sleep(10000);  // 10s，模拟程序的超长执行
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            // 执行解锁
            mylock.unlock();
            System.out.println("解锁成功" + Thread.currentThread().getId());
        }
        return "hello";
    }

    /**
     * 测试写锁
     *
     * @return
     */
    public String writeLock() {
        // 获取一个读写锁。同样，只要锁的名字是一样的就代表是同一把锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        // 加写锁，没有写成一个式子，方便解锁？
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        String uuid = "";
        try {
            // 模拟一个业务，这里就是向redis中存放一个数据，当然干啥都行
            uuid = UUID.randomUUID().toString();
            Thread.sleep(10000);
            stringRedisTemplate.opsForValue().set("writeLock", uuid, 30, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            // 无论业务代码是否成功都执行解锁
            // Redisson会删除掉rw-lock锁，留下一个最新的writeLock
            rLock.unlock();
        }
        return uuid;
    }

    /**
     * 测试读锁
     *
     * @return
     */
    public String readLock() {
        // 获取一个读写锁。同样，只要锁的名字是一样的就代表是同一把锁
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        // 加读锁，没有写成一个式子，方便解锁？
        RLock rLock = readWriteLock.readLock();
        rLock.lock();
        String s = "";
        try {
            s = stringRedisTemplate.opsForValue().get("writeLock");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            rLock.unlock();
        }
        return s;
    }

    /**
     * 测试闭锁
     */
    public String closeLock() throws InterruptedException {
        // 获取一个闭锁
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        // 使用.trySetCount(Long)来声明这个闭锁将会等待多少个线程，设置这个闭锁的计数器
        door.trySetCount(5);
        // 使用.await()等待闭锁完成
        // 设置了最大等待时间，如果超过20s就不等待了
        boolean await = door.await(20, TimeUnit.SECONDS);
        if (await) {
            return "闭锁内线程全部结束……";
        } else {
            // 如果是超过等待时间，则要手动删除闭锁
            door.delete();
            return "超出闭锁最大等待时间";
        }
    }

    /**
     * 用于在闭锁中执行的线程
     */
    public String gogogo(@PathVariable("id") Long id) {
        // 获取一个闭锁，只要锁的名字是一样的，获取的就是同一把锁
        RCountDownLatch door = redissonClient.getCountDownLatch("door");
        /**
         * 此处执行业务逻辑代码
         */
        // 一旦要结束这个线程了，就要将闭锁的计数器减一
        // 这里只有这一个线程，所以这个线程得执行五次，闭锁才能释放
        door.countDown();
        return id + "线程结束…";
    }

    /**
     * 测试信号量锁
     * 停车
     */
    public String park() throws InterruptedException {
        // 获取一个信号量锁
        RSemaphore park = redissonClient.getSemaphore("park");
        // 为信号锁设置信号容量。当然这个park()方法是业务方法，所以手动去redis加的这个键值
        // park.trySetPermits(3);
        // 尝试占领一个空信号量，如果不成功则会阻塞在这里，一直进行尝试，并设置最大等待10秒
        boolean tryAcquire = park.tryAcquire(1, 30, TimeUnit.SECONDS);
        if (tryAcquire) {
            return "停车成功……";
        } else {
            return "超出最大等待时间……";
        }
    }

    /**
     * 测试信号量锁
     * 离开
     */
    public String leave() {
        // 获取一个信号量锁
        RSemaphore park = redissonClient.getSemaphore("park");
        // 释放一个信号量
        park.release(1);
        return "离开车库……";
    }
}






























